/*
	page_alloc.c

	contains the global page allocation/deallocation functionality
	as well as some helper functions

	Author: Aidan Goddard 10/8/13
*/

#include"headers/kernel_tables.h"
#include"headers/bakery_lock.h"
#include"headers/scheduler_functions.h"
#include"../setup/headers/printf.h"

// old page allocation table
typedef struct OLD_PAGE_ALLOCATION OLD_PAGE_ALLOCATION;
struct __attribute__((packed)) OLD_PAGE_ALLOCATION
{
	// page allocation bitmap
	uint8_t bitmap[131072];
	uint32_t page_size;
	uint32_t highest_allocatable_page;
	uint32_t last_allocated_page;

};

// pointer to the old page allocation table
OLD_PAGE_ALLOCATION *old_allocation;

// returns the state of the page (0 = free, 1 = used)
static int GetPageState(uint64_t page)
{
	// get base address of page allocation bitmap
	uint64_t *ptr = (uint64_t*)old_allocation;

	// get the entry qword and bit in qword positions
	uint64_t entry = page / 64;
	uint64_t bit = page % 64;

	// get the dword and shift it and mask accordingly
	uint64_t to_return = ptr[entry];
	to_return = to_return >> bit;
	to_return = to_return & 1;

	// return it
	return (int)to_return;
}



// function to allocate a free page
// returns physical address of page
// returns  0 if there are no pages available
uint64_t PopFreePage()
{
    // get CPU number
    int cpu = GetCPUID();

    // grab lock on page system
    BakeryEnter(&(page_control->lock), cpu);

    // attempt to pop a free page
    uint64_t to_return = 0;
    while(1)
    {
        // check the current stack pointer
        if(page_control->stack_index == (512*1024))
        {
            // stack is empty, return 0
            to_return = 0;
            break;
        }

        // get the page address from the stack
        uint64_t new_page = page_stack[page_control->stack_index];
        page_control->stack_index++;
        page_control->free_pages--;

        // check its state
        uint64_t page_number = new_page / 0x200000;
        int state = page_control->pmap[page_number];

        if(state == PAGE_FREE)
        {
            // page is available, set it to used and return the address
            page_control->pmap[page_number] = PAGE_ALLOCATED_PRIVATE;
            to_return = new_page;
            break;
        }
    }

    // release lock on page system
    BakeryLeave(&(page_control->lock), cpu);

    // return the new page
    return to_return;
}

// function to deallocate a page
void PushFreePage(uint64_t page_address)
{
    // first align the address to 2MB (mask first 21 bits)
    page_address = page_address & 0xffffffffffe00000;

    // get CPU number
    int cpu = GetCPUID();

    // grab lock on page system
    BakeryEnter(&(page_control->lock), cpu);

    // check if page is mapped for hardware I/O and stuff
    int page = page_address / 0x200000;
    if(page_control->pmap[page] != PAGE_MAPPED)
    {
        // mark the page as free
        page_control->pmap[page] = PAGE_FREE;
        page_control->free_pages++;

        // then push it on the stack
        page_control->stack_index--;
        page_stack[page_control->stack_index] = page_address;
    }

    // release lock on page system
    BakeryLeave(&(page_control->lock), cpu);

    // finished, return
    return;
}

// function to return number of free pages
uint32_t GetFreePageCount()
{
    return page_control->free_pages;
}

// function to mark a page as mapped (for hardware I/O and stuff)
void MarkPageAsMapped(uint64_t page_address)
{
    // get page base address
    page_address = page_address & 0xffffffffffe00000;

    // get CPU number
    int cpu = GetCPUID();

    // grab lock on page system
    BakeryEnter(&(page_control->lock), cpu);

    // mark the page as mapped
    int page = page_address / 0x200000;
    page_control->pmap[page] = PAGE_MAPPED;

    // release lock on page system
    BakeryLeave(&(page_control->lock), cpu);

    // finished
    return;
}



// function to set up the global page allocation stack
void SetupGlobalPageControl()
{
	// get poiner to old allocation table
	old_allocation = (OLD_PAGE_ALLOCATION*)(KOFFSET + 0x600000);

	// get old highest page number
	uint32_t highest_page = old_allocation->highest_allocatable_page;
	highest_page = highest_page / 0x200000; // divide by page size to get the highest page number
	highest_page++;							// add 1 to get upper limit

	// check for 1TB limit
	if(highest_page > (512 * 1024))
        highest_page = 512 * 1024;

    // save it
    page_control->page_max = highest_page;

	// set the stack index to point to last entry
	page_control->stack_index = (512 * 1024) - 1;

	// zero free pages
	page_control->free_pages = 0;

	// zero the lock
	int i;
	for(i = 0; i < 32; i++)
	{
        page_control->lock.entering[i] = 0;
        page_control->lock.ticket[i] = 0;
	}

	// for each page in the old bitmap, either set it as disabled or set it as available and push it onto the stack
	for(i = 0; i < highest_page; i++)
	{
        // get the page state
        int state = GetPageState(i);

        // check its state
        if(state == 1)  // check for page is used
        {
            // page is used
            // set page map state
            page_control->pmap[i] = PAGE_DISABLED;
        }
        else
        {
            // page is free
            // set page map state
            page_control->pmap[i] = PAGE_FREE;

            // put the page address on the stack
            uint64_t paddress = i * 0x200000;
            page_stack[page_control->stack_index] = paddress;
            page_control->stack_index--;

            // increase free pages count
            page_control->free_pages++;

        }
	}

	// increase stack pointer to point to first page
	page_control->stack_index++;

	return;
}













